Python 学习笔记基础

基础

  1. 语法
  • #开头,注释
  • 缩进的语句视为代码块
  • 大小写敏感
  1. 数据类型和变量

    • TrueFalse首字母大写
    • andornot
    • 空值:None
  2. 字符串格式化%

    • %d整数

    • %f浮点数

    • %s字符串

    • %x十六进制整数

    • 补位

      1
      2
      3
      4
      >>> '%2d-%02d' % (3, 1)
      ' 3-01'
      >>> '%.2f' % 3.1415926
      '3.14'
  3. 数组:listtuple

    • list数组
      • len(list)得到长度
      • list[-2]获得倒数第二个元素
      • list.append(ele)往 list 中追加元素到末尾
      • list.insert(1, ele),把元素插入到指定的位置,比如索引号为1的位置
      • list.pop(),删除 list 末尾的元素,用pop()方法
      • list.pop(i)删除指定位置的元素,用pop(i)方法,其中i是索引位置
      • 元素的数据类型也可以不同,L = ['Apple', 123, True]
    • tuple数组:classmates = ('Michael', 'Bob', 'Tracy')
      • tuple一旦初始化就不能修改,代码更安全
  4. 条件判断和循环

    • if elif else

    • for in

    • while

    • range()函数

      1
      2
      3
      4
      5
      6
      >>> range(1,5) #代表从1到5(不包含5)
      [1, 2, 3, 4]
      >>> range(1,5,2) #代表从1到5,间隔2(不包含5)
      [1, 3]
      >>> range(5) #代表从0到5(不包含5)
      [0, 1, 2, 3, 4]
    • raw_inpit(str)读取的内容永远以字符串的形式返回

  5. dictset

    • dict就是map,用 key-value 的形式存储。

      1
      d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
    • 根据key获得value

      • []:一旦key不存在就会报错
      • get()函数:如果 key 不存在,可以返回 None,或者自己指定的 value(作为第二个参数传入)
    • set是一组 key 的集合,但不存储 value,key 不能重复。

      • 需要 list 作为输入
      • add(key)函数用来往里面添加元素,自动忽略重复
      • remove(key)函数用来删除元素
      • &操作用来做交集
      • |操作用来做并集

函数

  1. Python 内置数据类型转换函数:如int()

  2. Python 可以给函数赋别名,如:a = abs

  3. 定义函数,def

    1
    2
    3
    4
    5
    def my_abs(x):
    if x >= 0:
    return x
    else:
    return -x

    如果没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return

  4. 空函数,pass

    1
    2
    def nop():
    pass
  5. 类型检查,isinstance

    1
    2
    3
    4
    5
    6
    7
    def my_abs(x):
    if not isinstance(x, (int, float)):
    raise TypeError('bad operand type')
    if x >= 0:
    return x
    else:
    return -x
  6. Python 函数可以返回多个值:

    1
    2
    3
    4
    5
    6
    import math

    def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny
    1
    2
    3
    >>> x, y = move(100, 100, 60, math.pi / 6)
    >>> print x, y
    151.961524227 70.0

    事实上,它并没有返回多个值,而是返回了一个 tuple

  7. Python 具有默认参数:

    1
    2
    3
    4
    5
    6
    def power(x, n=2):
    s = 1
    while n > 0:
    n = n - 1
    s = s * x
    return s

    默认参数必须指向不变对象,不然默认参数就会不断变化,例子:

    1
    2
    3
    def add_end(L=[]):
    L.append('END')
    return L
    1
    2
    3
    4
    5
    6
    >>> add_end()
    ['END']
    >>> add_end()
    ['END', 'END']
    >>> add_end()
    ['END', 'END', 'END']
  8. 可变参数,*

    1
    2
    3
    4
    5
    def calc(*numbers):
    sum = 0
    for n in numbers:
    sum = sum + n * n
    return sum
    1
    2
    3
    4
    >>> calc(1, 2)
    5
    >>> calc()
    0

    在函数内部,参数numbers接收到的是一个 tuple。

    如果已经有一个 list 或者 tuple,可以这么写:

    1
    2
    3
    >>> nums = [1, 2, 3]
    >>> calc(*nums)
    14
  9. 关键字参数:**,会把多与参数自动组装成一个dict

    1
    2
    def person(name, age, **kw):
    print 'name:', name, 'age:', age, 'other:', kw
    1
    2
    3
    4
    5
    6
    >>> person('Michael', 30)
    name: Michael age: 30 other: {}
    >>> person('Bob', 35, city='Beijing')
    name: Bob age: 35 other: {'city': 'Beijing'}
    >>> person('Adam', 45, gender='M', job='Engineer')
    name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
    1
    2
    3
    >>> kw = {'city': 'Beijing', 'job': 'Engineer'}
    >>> person('Jack', 24, **kw)
    name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
  10. 参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数

高级特性

切片(Slice )

1
2
3
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
  • L[0:3]表示,从索引 0 开始取,直到索引 3 为止,但不包括索引 3。即索引 0,1,2,正好是 3 个元素。
  • 如果第一个索引是 0,还可以省略。
  • L[-1]取倒数第一个元素,也支持倒数切片:L(-2:)
  • 只写[:]就可以原样复制一个 list
  • L[:10:2]表示前十个元素,每两个取一个:[0,2,4,6,8]
  • tuple 也可以用切片,操作结果也是 tuple
  • 字符串也支持切片

迭代(Iteration)

  • 只要是可迭代对象(list,tuple,dict,set,字符串)都可以用for...in...迭代

  • 默认情况下,dict 迭代的是 key。

    • 如果要迭代 value,可以用for value in d.itervalues()
    • 如果要同时迭代 key 和 value,可以用for k, v in d.iteritems()
  • 判断一个对象是否是可迭代对象:

    1
    2
    3
    4
    from collections import Iterable
    isinstance('abc', Iterable) # str是否可迭代 True
    isinstance([1,2,3], Iterable) # list是否可迭代 True
    isinstance(123, Iterable) # 整数是否可迭代 False
  • 拥有下标的循环:

    1
    2
    for i, value in enumerate(['A', 'B', 'C']):
    print i, value
  • for循环同时引用两个变量:

    1
    2
    for x, y in [(1, 1), (2, 4), (3, 9)]:
    print x, y

列表生成式(List Comprehensions)

  • [x * x for x in range(1, 11)][1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
  • 加上判断:[x * x for x in range(1, 11) if x % 2 == 0][4, 16, 36, 64, 100]
  • 两层循环(可以用来生成全排列):[m + n for m in 'ABC' for n in 'XYZ']['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

生成器(Generator)

生成器里面装了用来生成一个 list 的算法,这样就不必创建完整的 list,从而节省大量的空间,一边循环,一边计算。

1
2
3
4
5
g = (x * x for x in range(3))
g.next() # 0
g.next() # 1
g.next() # 4
g.next() # StopIteration

也可以用for循环:

1
2
3
g = (x * x for x in range(10))
for n in g:
print n
  • 定义 generator 的另一种方法,yield

    1
    2
    3
    4
    5
    6
    7
    # print Fibonacci list
    def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
    print b
    a, b = b, a + b
    n = n + 1

    改写成 generator:

    1
    2
    3
    4
    5
    6
    7
    # Fibonacci generator
    def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
    yield b
    a, b = b, a + b
    n = n + 1
    1
    2
    >>> fib(6)
    <generator object fib at 0x104feaaa0>
  • generator 的执行顺序:

    变成 generator 的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    >>> def odd():
    ... print 'step 1'
    ... yield 1
    ... print 'step 2'
    ... yield 3
    ... print 'step 3'
    ... yield 5
    ...
    >>> o = odd()
    >>> o.next()
    step 1
    1
    >>> o.next()
    step 2
    3
    >>> o.next()
    step 3
    5
    >>> o.next()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    StopIteration

函数式编程(Functional Programming)

Python 对函数式编程提供部分支持。由于 Python 允许使用变量,因此,Python 不是纯函数式编程语言。

高阶函数

  • map第一个参数是函数,第二个参数是可迭代对象,返回一个 list
  • reduce第一个参数是函数,第二个参数是可迭代对象,函数的两个参数(result, item)
  • filter
  • sorted第一个参数是可迭代对象,第二个参数是比较函数(有默认值)

返回函数

  • Python 也有闭包的概念

匿名函数(lambda)

lambda x: x * x等同于:

1
2
def f(x):
return x * x

装饰器(Decorator)

@

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper
1
2
3
@log
def now():
print '2017-08-14'

相当于执行了:

1
now = log(now)

但此时,now.__name__已经被替换成了wrapper,所以完整的写法:

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper

偏函数(Partial function)

可以固定某个参数的默认值,比如下方的例子,固定了int函数的base参数。

1
int2 = functools.partial(int, base=2)

模块

Python 所有内置函数

模块:一个文件就是一个模块

包:就是不同的文件夹,但每个包目录下面都要有一个__init__.py的文件,否则就是一个普通目录而不是一个包。__init.__.py可以是空文件,也可以有代码,因为__init__.py本身就是一个模块,它的模块名就是它的父目录。比如如下的文件结构:

文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils

引入模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
args = sys.argv
if len(args)==1:
print 'Hello, world!'
elif len(args)==2:
print 'Hello, %s!' % args[1]
else:
print 'Too many arguments!'

if __name__=='__main__':
test()

第 1 行和第 2 行是标准注释,第 1 行注释可以让这个hello.py文件直接在 Unix/Linux/Mac 上运行,第 2 行注释表示.py 文件本身使用标准 UTF-8 编码;

第 4 行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第 6 行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

sys.argv用 list 存储了命令行的所有参数。python hello.py对应['hello.py']python hello.py Michael对应 ['hello.py', 'Michael]

最后两行:当用命令行运行hello模块时,Python 解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,则不会。

  • 别名:cStringIO是 C 写的,速度更快,这样就可以优先导入cStringIO。如果有些平台不提供cStringIO,还可以降级使用StringIO

    1
    2
    3
    4
    try:
    import cStringIO as StringIO
    except ImportError: # 导入失败会捕获到ImportError
    import StringIO

    还有类似simplejson这样的库,在 Python 2.6 之前是独立的第三方库,从 2.6 开始内置

    1
    2
    3
    4
    try:
    import json # python >= 2.6
    except ImportError:
    import simplejson as json # python <= 2.5
  • 作用域:

    前缀_表示私有函数,不应该被外部引用。

安装第三方模块

pip

pip install是全局安装

默认情况下,Python 解释器会搜索当前目录、所有已安装的内置模块和第三方模块。搜索路径存放在sys模块的path变量中,可以添加要搜索的目录:

1
2
import sys
sys.path.append('/Users/michael/my_py_scripts')

__future__模块

Python 提供了__future__模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性,比如:

1
2
3
4
5
6
7
8
# still running on Python 2.7

from __future__ import unicode_literals

print '\'xxx\' is unicode?', isinstance('xxx', unicode)
print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
print '\'xxx\' is str?', isinstance('xxx', str)
print 'b\'xxx\' is str?', isinstance(b'xxx', str)
1
2
3
4
5
from __future__ import division

print '10 / 3 =', 10 / 3
print '10.0 / 3 =', 10.0 / 3
print '10 // 3 =', 10 // 3

面向对象编程

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score

def print_score(self):
print '%s: %s' % (self.name, self.score)

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score() # Bart Simpson: 59
lisa.print_score() # Lisa Simpson: 87

类和实例

1
2
3
4
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。

__init__函数是构造函数。

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。

访问限制

  • 在 Python 中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
  • 变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是 private 变量,所以,不能用__name____score__这样的变量名。
  • 以一个下划线开头的实例变量名,比如_name,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
  • 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为 Python 解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量

继承与多态

OOP 的特性,不作多余解释。

获取对象信息

  • type()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> type(123)
    <type 'int'>
    >>> type('str')
    <type 'str'>
    >>> type(None)
    <type 'NoneType'>
    >>> type(abs)
    <type 'builtin_function_or_method'>
    >>> type(a)
    <class '__main__.Animal'>

    types模块types.StringTypetypes.UnicodeTypetypes.ListTypetypes.TypeType(所有类型本身的类型就是TypeType)

  • isinstance()

    1
    2
    3
    4
    5
    6
    7
    8
    # object -> Animal -> Dog -> Husky
    a = Animal()
    d = Dog()
    h = Husky()

    isinstance(h, Husky) # True
    isinstance(h, Dog) # True
    isinstance(h, Animal) # True

    判断一个变量是否是某些类型中的一种

    1
    2
    isinstance('a', (str, unicode))		# True
    isinstance(u'a', (str, unicode)) # True
  • dir():获得对象的所有属性和方法,返回一个 list

    类似__xxx__的属性和方法在 Python 中都是有特殊用途的。比如__len__方法返回长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法:

    1
    2
    3
    4
    5
    6
    class MyObject(object):
    def __len__(self):
    return 100

    obj = MyObject()
    len(obj) # 100
  • hasattrsetattrgetattr

    1
    2
    3
    hasattr(obj, 'x')		# 有属性'x'吗?
    setattr(obj, 'y', 19) # 设置一个属性'y'
    getattr(obj, 'z') # 获取属性'z'

__slots__

  • 给 class 绑定新的方法,MethodType

    1
    2
    3
    4
    def set_score(self, score):
    self.score = score

    Student.set_score = MethodType(set_score, None, Student)
  • 限制类能添加的属性,__slots

    1
    2
    class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

    对子类不起作用。

@property

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
1
2
3
4
5
6
s = Student()
s.score = 60 # OK,实际转化为s.set_score(60)
s.score # OK,实际转化为s.get_score() 60
s.score = 9999
# Traceback (most recent call last):
# ValueError: score must between 0 ~ 100!

不定义 setter 的话,就是一个只读属性

多重继承

1
2
3
4
5
class Dog(Mammal, Runnable):
pass

class Bat(Mammal, Flyable):
pass

定制类

  • __str__()方法用于自动类型转换

    1
    2
    3
    4
    5
    class Student(object):
    def __init__(self, name):
    self.name = name
    def __str__(self):
    return 'Student object (name: %s)' % self.name
  • __iter__(),配合next()方法,可以对类进行for...in操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Fib(object):
    def __init__(self):
    self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
    return self # 实例本身就是迭代对象,故返回自己

    def next(self):
    self.a, self.b = self.b, self.a + self.b # 计算下一个值
    if self.a > 100000: # 退出循环的条件
    raise StopIteration();
    return self.a # 返回下一个值
    1
    2
    for n in Fib():
    print n
  • __getitem__():可以类似 list 那样,用方括号[]取出对应的元素。

    1
    2
    3
    4
    5
    6
    class Fib(object):
    def __getitem__(self, n):
    a, b = 1, 1
    for x in range(n):
    a, b = b, a + b
    return a
    1
    2
    3
    4
    5
    6
    f = Fib()
    f[0] # 1
    f[1] # 1
    f[10] # 89
    f[:10] # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    f[:10:2] # 未对step做处理,[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

    __getitem__()也没有对负数进行处理。

    __getitem__()也可以将对象处理成一个 dict:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Test(object):
    def __init__(self, a, b):
    self.a = a
    self.b = b

    def __getitem__(self, n):
    if n == 'a':
    return self.a
    else:
    return self.b
    1
    2
    f = Test('aa', 'bb')
    print f['a'] # 'aa'

    与之对应的是__setitem__()方法,把对象视作 list 或 dict 来对集合赋值。

    还有一个__delitem__()方法,用于删除某个元素。

  • __getattr__(),用于处理对象没有的属性或方法:

    1
    2
    3
    4
    5
    6
    class Test(object):
    def __init__(self, name):
    self.name = name

    def __getattr__(self, attr):
    print 'Test doesn\'t have attribute: %s' % attr
    1
    2
    test = Test('aa')
    test.age # Test doesn't have attribute: age

    同样也可以返回方法。

    只有在没有找到属性的情况下,才调用__getattr__

  • __call__()使得实例本身可以称为一个函数。

    1
    2
    3
    4
    5
    6
    7
    class Student(object):
    def __init__(self, name):
    self.name = name

    def __call__(self, age):
    self.age = age
    print('My name is %s and my age is %s' % self.name, age)
    1
    2
    student = Student('Jackie')
    student(25) # My name is Jackie and my age is 25

    可以用callbale()来判断一个对象是否能被调用。

元类

  • type()不仅可以用来查看某个对象的类型,也可以用来创建新的类:

    1
    2
    3
    4
    5
    6
    7
    8
    def fn(self, name='world'): # 先定义函数
    print('Hello, %s.' % name)

    Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
    h = Hello()
    h.hello() # Hello, world.
    print(type(Hello)) # <type 'type'>
    print(type(h)) # <class '__main__.Hello'>

    传入三个参数:

    • 类名
    • 继承的父类的 tuple
    • 属性和方法的 dict

    通过type()函数创建的类和直接写 class 是完全一样的

  • metaclass元类,暂时不学

错误、调试和测试

错误处理

try...except...finally...

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
print 'try...'
r = 10 / int('a')
print 'result:', r
except ValueError, e:
print 'ValueError:', e
except ZeroDivisionError, e: # 可以写多条except
print 'ZeroDivisionError:', e
else: # 未出错就会执行
print 'no error!'
finally:
print 'finally...' # 无论是否出错,都会执行
print 'END'

except还会捕获异常的子类,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。

Exception hierarchy 异常继承关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StandardError
| +-- BufferError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| | +-- VMSError (VMS)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning

logging

Python 内置的logging模块可以非常容易地记录错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# err.py
import logging

def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except StandardError, e:
logging.exception(e)

main()
print 'END'
1
2
3
4
5
6
7
8
9
10
11
$ python err.py
ERROR:root:integer division or modulo by zero
Traceback (most recent call last):
File "err.py", line 12, in main
bar('0')
File "err.py", line 8, in bar
return foo(s) * 2
File "err.py", line 5, in foo
return 10 / int(s)
ZeroDivisionError: integer division or modulo by zero
END

自己编写错误

1
2
3
4
5
6
7
8
9
# err.py
class FooError(StandardError):
pass

def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n

继续向上抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# err.py
def foo(s):
n = int(s)
return 10 / n

def bar(s):
try:
return foo(s) * 2
except StandardError, e:
print 'Error!'
raise

def main():
bar('0')

main()

调试

断言assert

1
2
3
4
5
6
7
8
# err.py
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n

def main():
foo('0')

assert语句本身会抛出异常,AssertionError

可以用-O参数关闭 assert:python -O err.py

日志logging

几个级别:debuginfowarningerror

可以指定级别,当我们指定level=INFO时,logging.debug就不起作用了。

1
2
import logging
logging.basicConfig(level=logging.INFO)

单元测试

unittest模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# mydict_test.py

import unittest
from mydict import Dict

class TestDict(unittest.TestCase):

def test_init(self):
d = Dict(a=1, b='test')
self.assertEquals(d.a, 1)
self.assertEquals(d.b, 'test')
self.assertTrue(isinstance(d, dict))

def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEquals(d.key, 'value')

def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEquals(d['key'], 'value')

def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']

def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
  • 测试类:从unittest.TestCase继承。

  • test开头的方法才会被认为是测试方法,才能被执行

  • 用断言来判断是否符合期望,assertEquals() assertTrue() ,以及抛出异常:

    1
    2
    with self.assertRaises(AttributeError):
    value = d.empty

运行单元测试

  1. 在单元测试文件mydict_test.py下加上两行代码:

    1
    2
    if __name__ == '__main__':
    unittest.main()

    这样就可以像正常脚本一样运行了$ python mydict_test.py

  2. 用参数-m unittest$ python -m unittest mydict_test.py

setUp()tearDown()

分别会在每调用一个测试方法的前后分别被执行。

文档测试

doctest模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# mydict.py
class Dict(dict):
'''
Simple dict but also support access as x.y style.

>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)

def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

def __setattr__(self, key, value):
self[key] = value

if __name__=='__main__':
import doctest
doctest.testmod()

运行测试:python mydict.py

IO 编程

文件读写

1
2
3
f = open('/Users/michael/test.txt', 'r')
f.read() # 一次读取文件的全部内容,用一个str对象表示
f.close() # 文件使用完毕后必须关闭

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。最好用try...finally来写:

1
2
3
4
5
6
try:
f = open('/path/to/file', 'r')
print f.read()
finally:
if f:
f.close()

Python 引入了with语句来帮助调用:

1
2
with open('/path/to/file', 'r') as f:
print f.read()
  • read(size)可以读取 size 个字节的内容
  • readline()可以读取一行内容
  • readlines()一次读取所有内容并按行返回list

操作文件和目录

os模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
# 系统
os.name # 操作系统名字,posix->(Linux/Unix/Mac OS X),nt->Windows
os.uname() # 详细系统信息,Windows不提供该接口
os.environ # 系统环境连梁
os.getenv('PATH') # 获取某个环境变量的值

# 文件和目录
os.path.abspath('.') # 查看当前目录的绝对路径
os.mkdir(os.path.join(os.path.abspath('.'), 'testdir')) # 创建新目录
os.rmdir(os.path.join(os.path.abspath('.'), 'testdir')) # 删除该目录
# 把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数
# 要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数
# 这样可以正确处理不同操作系统的路径分隔符。

# os.path.splitext()可以直接让你得到文件扩展名
os.path.splitext('/path/to/file.txt') # ('/path/to/file', '.txt')
  • os模块不提供拷贝文件的接口,而shutil模块提供了copyfile()的函数。

序列化(pickling)

序列化:把变量从内存中变成可存储或传输的过程。

cPicklepickle

1
2
3
4
try:
import cPickle as pickle
except ImportError:
import pickle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
d = dict(name='Bob', age=20, score=88)

# pickle.dumps()方法把任意对象序列化成一个str
s = pickle.dumps(d) # "(dp0\nS'age'\np1\nI20\nsS'score'\np2\nI88\nsS'name'\np3\nS'Bob'\np4\ns."
# pickle.load(s() 反序列化
pickle.loads(s) # {'age': 20, 'score': 88, 'name': 'Bob'}

# pickle.dump()直接把对象序列化后写入一个file-like Object
f = open('dump.txt', 'wb')
s = pickle.dump(d, f)
f.close()
# pickle.load() 反序列化
f = open('dump.txt', 'rb')
pickle.load(f)
f.close()

JSON

内置的json模块

1
2
3
4
5
import json
d = dict(name='Bob', age=20, score=88)
json.dumps(d) # '{"age": 20, "score": 88, "name": "Bob"}'
# dump则可以用于写入
# loads、load等

定制 JSON 序列化

将某个 class 的实例序列化成 JSON:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json

class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}

s = Student('Bob', 20, 88)
print(json.dumps(s, default=student2dict))
  • 偷懒的方法:利用__dict__属性,它就是一个dict,用来存储实例变量。

反序列化:

1
2
3
4
5
def dict2student(d):
return Student(d['name'], d['age'], d['score'])

json_str = '{"age": 20, "score": 88, "name": "Bob"}'
print(json.loads(json_str, object_hook=dict2student))

进程和线程

多进程(multiprocessing)

Unix/Linux 操作系统提供了一个fork()系统调用,子进程永远返回0,而父进程返回子进程的 ID。子进程只需要调用getppid()就可以拿到父进程的 ID。

1
2
3
4
5
6
7
8
9
# multiprocessing.py
import os

print 'Process (%s) start...' % os.getpid()
pid = os.fork()
if pid==0:
print 'I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid())
else:
print 'I (%s) just created a child process (%s).' % (os.getpid(), pid)

multiprocessing模块 - 跨平台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
print 'Run child process %s (%s)...' % (name, os.getpid())

if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Process(target=run_proc, args=('test',))
print 'Process will start.'
p.start()
p.join() # join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
print 'Process end.'
1
2
3
4
Parent process 928.
Process will start.
Run child process test (929)...
Process end.

Pool 进程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print 'Run task %s (%s)...' % (name, os.getpid())
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print 'Task %s runs %0.2f seconds.' % (name, (end - start))

if __name__=='__main__':
print 'Parent process %s.' % os.getpid()
p = Pool()
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print 'Waiting for all subprocesses done...'
p.close() # 调用close()之后就不能继续添加新的Process了
p.join() # 调用join()之前,一定要调用close()
print 'All subprocesses done.'

Pool默认大小是 CPU 核数,当创建多于Pool大小的进程数时,后面的进程就会等待前面的进程执行完成再执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
Parent process 669.
Waiting for all subprocesses done...
Run task 0 (671)...
Run task 1 (672)...
Run task 2 (673)...
Run task 3 (674)...
Task 2 runs 0.14 seconds.
Run task 4 (673)...
Task 1 runs 0.27 seconds.
Task 3 runs 0.86 seconds.
Task 0 runs 1.41 seconds.
Task 4 runs 1.91 seconds.
All subprocesses done.

也可以设置p = Pool(5)同时运行 5 个进程。

进程间通讯

Queue策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
for value in ['A', 'B', 'C']:
print 'Put %s to queue...' % value
q.put(value)
time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
while True:
value = q.get(True)
print 'Get %s from queue.' % value

if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()

还有Pipes等策略

多线程

threading高级模块对thread模块进行了封装,较常使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time, threading

# 新线程执行的代码:
def loop():
print 'thread %s is running...' % threading.current_thread().name
n = 0
while n < 5:
n = n + 1
print 'thread %s >>> %s' % (threading.current_thread().name, n)
time.sleep(1)
print 'thread %s ended.' % threading.current_thread().name

print 'thread %s is running...' % threading.current_thread().name
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print 'thread %s ended.' % threading.current_thread().name
1
2
3
4
5
6
7
8
9
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import time, threading

# 假定这是你的银行存款:
balance = 0

def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n

lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print balance

多核 CPU

Python 因为存在 global interpret lock(GIL 锁)的关系,虽然可以多线程执行,但无法真正利用多核。每个进程最多利用 2 两个核。

ThreadLocal

为每个线程单独建立作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import threading

# 创建全局ThreadLocal对象:
local_school = threading.local()

def process_student():
print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)

def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()

t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start() # Hello, Alice (in Thread-A)
t2.start() # Hello, Bob (in Thread-B)
t1.join()
t2.join()